# $Id$
#
#  MapView.py
#  Tarjan
#
#  Created by Anders Hellerup Madsen on 3/24/08.
#  Copyright (c) 2008 __MyCompanyName__. All rights reserved.
#

from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *

import re
import operator
import louie # message dispatch system, http://louie.berlios.de/
from threading import Timer
from directions import Directions
from map import Map
from commandParser import TarjanCommandParser

#               from IPython.Shell import IPShellEmbed
#               ipshell = IPShellEmbed()
#               ipshell()


class MapView(NSView):

    def isFlipped(self): return YES
    def acceptsFirstResponder(self):
        return YES
    def resignFirstResponder(self):
        self.setNeedsDisplay_(YES)
        return YES
    def becomeFirstResponder(self):
        self.setNeedsDisplay_(YES)
        return YES

    map = property(fget=lambda self:self.dataSource.content())

    dataSource = objc.IBOutlet()
    cellSize = 20
    wall_types = [ 'clear', 'wall', 'door', 'locked_door', 'gate', 'arch', 'secret' ]
    cell_types = [ 'clear', 'empty', 'darkness', 'stairs_up', 'stairs_down' ]

    def initWithFrame_(self, frame):
        self = super(MapView, self).initWithFrame_(frame)
        if self:
            self.selectedCell = None
            self.facing = Directions.North
            self.command_parser = TarjanCommandParser()
            self.command_parser.table = self.setup_command_table()

            # create receivers for louie, because it doesn't work with PyObjC selectors
            setattr(self, 'updateCellReceiver', lambda s:self.updateCell(s))
        return self

    def registerEventListeners(self):
        for cell in self.map:
            louie.connect(self.updateCellReceiver, 'change', cell)

    def unregisterEventListeners(self):
        for cell in self.map:
            try: 
                louie.disconnect(self.updateCellReceiver, 'change', cell)
            except(louie.error.DispatcherKeyError):
                pass

    def resize(self):
        """resize the mapview, so it fits the size of the current map"""
        last_cell = self.rectFromCell(self.map.getCell(self.map.width - 1, self.map.height - 1))
        width = last_cell.origin.x + last_cell.size.width + self.cellSize / 2
        height = last_cell.origin.y + last_cell.size.height + self.cellSize / 2
        self.setFrameSize_( (width, height) )

    def drawRect_(self, draw_rect):

        if self.selectedCell == None:
            self.selectedCell = self.map.getCell(0,0)

        if self.window().firstResponder() == self:
            NSColor.keyboardFocusIndicatorColor().set()
            NSBezierPath.setDefaultLineWidth_(4)
            NSBezierPath.strokeRect_(self.bounds())
            NSBezierPath.setDefaultLineWidth_(1)

        # clear drawing area
        NSColor.whiteColor().set()
        NSBezierPath.fillRect_(draw_rect)

        # iterate through cells and draw if cells rect intersects with drawing area
        for cell in self.map:
                rect = self.rectFromCell(cell)
                if not NSIntersectsRect(rect, draw_rect):
                    continue
                (x,y), (w,h) = rect.origin, rect.size
                getattr(self, "renderCell_" + cell.value)(rect)
                getattr(self, "renderWall_" + cell.North)(NSMakeRect(x  , y  , w, 0))
                getattr(self, "renderWall_" + cell.South)(NSMakeRect(x  , y+h, w, 0))
                getattr(self, "renderWall_" + cell.East )(NSMakeRect(x+w, y  , 0, h))
                getattr(self, "renderWall_" + cell.West )(NSMakeRect(x  , y  , 0, h))

                font = NSFont.boldSystemFontOfSize_(8)
                s = NSString.stringWithString_(cell.notes)
                s.drawInRect_withAttributes_(rect, {
                    NSFontAttributeName: font,
                    NSForegroundColorAttributeName: NSColor.blueColor()
                })
        
                if cell is self.selectedCell:
                    self.renderPointer()
        return

    def rectFromCell(self, cell):
        x, y = [self.cellSize * p for p in cell.pos] 
        rect = NSMakeRect(x + self.cellSize / 2, y + self.cellSize / 2, self.cellSize, self.cellSize)
        return rect

    def updateCell(self, cell):
        rect = self.rectFromCell(cell)
        rect.size = [i+1 for i in rect.size]
        self.setNeedsDisplayInRect_(rect)


###### POINTER RENDERRING ########
    def renderPointer(self):
        rect = self.rectFromCell(self.selectedCell)
        (x,y), (w,h) = rect.origin, rect.size

        xform = NSAffineTransform.transform()
        xform.translateXBy_yBy_(x + w/2, y + h/2)
        xform.rotateByDegrees_(self.facing * 90)
        xform.concat()

        path = NSBezierPath.bezierPath()
        path.moveToPoint_(NSMakePoint(-w/8, h/4))
        path.lineToPoint_(NSMakePoint(   0,-h/4))
        path.lineToPoint_(NSMakePoint( w/8, h/4))
        path.closePath()

        NSColor.blackColor().setStroke()
        NSColor.greenColor().setFill()
        path.setLineWidth_(1)
        path.stroke()
        path.fill()

        xform.invert()
        xform.concat()

######## CELL RENDERING ##########

    def renderCell_clear(self, rect):
        NSColor.colorWithCalibratedWhite_alpha_(0.90, 1).set()
        NSBezierPath.fillRect_(rect)

    def renderCell_empty(self, rect):
        pass

    def renderCell_darkness(self, rect):
        NSColor.colorWithCalibratedWhite_alpha_(0.65, 1).set()
        NSBezierPath.fillRect_(rect)

    def renderCell_stairs_up(self, rect):
        s = NSString.stringWithString_("UP")
        font = NSFont.boldSystemFontOfSize_(14)
        s.drawInRect_withAttributes_(rect, {
            NSFontAttributeName: font,
            NSForegroundColorAttributeName: NSColor.greenColor()
        })

    def renderCell_stairs_down(self, rect):
        s = NSString.stringWithString_("DN")
        font = NSFont.boldSystemFontOfSize_(12)
        s.drawInRect_withAttributes_(rect, {
            NSFontAttributeName: font,
            NSForegroundColorAttributeName: NSColor.greenColor()
        })


######## WALL RENDERING ##########

    def renderWall_clear(self, rect):
        (x,y), (w,h) = rect.origin, rect.size
        NSColor.blackColor().set()
        NSBezierPath.fillRect_(NSMakeRect(x, y, 1.0, 1.0))
        NSBezierPath.fillRect_(NSMakeRect(x+w, y+h, 1.0, 1.0))

    def renderWall_wall(self, rect):
        x,y = rect.origin
        w,h = [i or 1 for i in rect.size]
        NSColor.blackColor().set()
        NSBezierPath.fillRect_(NSMakeRect(x, y, w, h))

    def renderWall_gate(self, rect):
        (x, y), (w, h) = rect
        size = 7

        NSColor.blackColor().set()
        if rect.size.width > rect.size.height:
            NSBezierPath.fillRect_(NSMakeRect(x, y - 1, w, 1))
            NSBezierPath.fillRect_(NSMakeRect(x, y + 2, w, 1))
        else:
            NSBezierPath.fillRect_(NSMakeRect(x - 2, y, 1, h))
            NSBezierPath.fillRect_(NSMakeRect(x + 2, y, 1, h))

    def renderWall_door(self, rect):
        (x, y), (w, h) = rect
        size = 7

        NSColor.blackColor().set()
        if rect.size.width > rect.size.height:
            NSBezierPath.fillRect_(NSMakeRect(x, y, w, 1))
            NSBezierPath.fillRect_(NSMakeRect(x + w / 2, y - size / 2, 1, size))
        else:
            NSBezierPath.fillRect_(NSMakeRect(x, y, 1, h))
            NSBezierPath.fillRect_(NSMakeRect(x - size / 2,  y + h / 2, size, 1))

    def renderWall_locked_door(self, rect):
        (x, y), (w, h) = rect
        size = 7

        NSColor.blackColor().set()
        if rect.size.width > rect.size.height:
            NSBezierPath.fillRect_(NSMakeRect(x, y, w, 1))
            NSBezierPath.fillRect_(NSMakeRect(x + w / 2 - 2, y - size / 2, 1, size))
            NSBezierPath.fillRect_(NSMakeRect(x + w / 2 + 2, y - size / 2, 1, size))
        else:
            NSBezierPath.fillRect_(NSMakeRect(x, y, 1, h))
            NSBezierPath.fillRect_(NSMakeRect(x - size / 2,  y + h / 2 - 2, size, 1))
            NSBezierPath.fillRect_(NSMakeRect(x - size / 2,  y + h / 2 + 2, size, 1))

    def renderWall_arch(self, rect):
        (x, y), (w, h) = rect

        NSColor.blackColor().set()
        if rect.size.width > rect.size.height:
            NSBezierPath.fillRect_(NSMakeRect(x, y, w / 4, 1))
            NSBezierPath.fillRect_(NSMakeRect(x + 3 * w / 4, y, w / 4, 1))
        else:
            NSBezierPath.fillRect_(NSMakeRect(x, y, 1, h / 4))
            NSBezierPath.fillRect_(NSMakeRect(x, y + 3 * h / 4, 1, h / 4))
            
    def renderWall_secret(self, rect):
        (x, y), (w, h) = rect
        font = NSFont.systemFontOfSize_(15)
        s = NSString.stringWithString_('s')

        NSColor.blackColor().set()
        if rect.size.width > rect.size.height:
            NSBezierPath.fillRect_(NSMakeRect(x, y, w, 1))
            xform = NSAffineTransform.transform()
            xform.translateXBy_yBy_(x + w + 2, y - 4)
            xform.rotateByDegrees_(90)
            xform.concat()
            s.drawAtPoint_withAttributes_((0, 0), { NSFontAttributeName: font })
            xform.invert()
            xform.concat()
        else:
            NSBezierPath.fillRect_(NSMakeRect(x, y, 1, h))
            s.drawAtPoint_withAttributes_((x - 3, y), { NSFontAttributeName: font })
        

######### KEYBOARD HANDLING #######

    def setup_command_table(self):
        self.command_table = {
            '.*\x1b|.* $':   (self.command_parser.clear,),  # Escape or space
            'h':             (self.movePointer, Directions.West),
            'j':             (self.movePointer, Directions.South),
            'k':             (self.movePointer, Directions.North),
            'l':             (self.movePointer, Directions.East),
            'dn':            (self.setNote, ''),
            'dw':            (self.clearCurrentCellWalls, ),
            'dd':            (self.clearCurrentCell, ),
            'dc':            (self.clearCurrentCell, False),
            'Mh':            (self.moveMap, Directions.West),
            'Mj':            (self.moveMap, Directions.South),
            'Mk':            (self.moveMap, Directions.North),
            'Ml':            (self.moveMap, Directions.East),
            ' <RIGHT>':      (self.turnPointer, 1),
            ' <LEFT>':       (self.turnPointer, -1),
            ' <UP>':         (lambda self: self.movePointer(self.facing), self),
            ' <DOWN>':       (lambda self: self.movePointer(~self.facing), self),
            ' <SHIFT-RIGHT>':(lambda self: self.changeWallInCurrentCell(self.facing + 1), self),
            ' <SHIFT-LEFT>': (lambda self: self.changeWallInCurrentCell(self.facing - 1), self),
            ' <SHIFT-UP>':   (lambda self: self.changeWallInCurrentCell(self.facing), self),
            ' <SHIFT-DOWN>': (lambda self: self.changeWallInCurrentCell(~self.facing), self),
            ' <SHIFT-ALT-RIGHT>':(lambda self: self.changeWallInCurrentCell(self.facing + 1, False), self),
            ' <SHIFT-ALT-LEFT>': (lambda self: self.changeWallInCurrentCell(self.facing - 1, False), self),
            ' <SHIFT-ALT-UP>':   (lambda self: self.changeWallInCurrentCell(self.facing, False), self),
            ' <SHIFT-ALT-DOWN>': (lambda self: self.changeWallInCurrentCell(~self.facing, False), self),
            '\x0e| <ALT-UP>':    (self.changeCurrentCell,),       # ctrl-n
            '\x10| <ALT-DOWN>':  (self.changeCurrentCell, False), # ctrl-p
            'n(.*)[\n\r]':   (self.setNote,),
            '[gG](\d+),(\d+)[\n\r]': (self.movePointerTo,),
            'R(\d+),(\d+)[\n\r]': (self.resizeMap,),

        }
        return self.command_table

    def keyDown_(self, event):

        modifiers = event.modifierFlags()
        keypress = ''

        if modifiers & NSNumericPadKeyMask:

            self.command_parser.clear()

            keypress = ' <'
            if modifiers & NSShiftKeyMask: keypress += 'SHIFT-'
            if modifiers & NSAlternateKeyMask: keypress += 'ALT-'
            keypress += {
                    NSUpArrowFunctionKey: 'UP',
                    NSRightArrowFunctionKey: 'RIGHT',
                    NSDownArrowFunctionKey: 'DOWN',
                    NSLeftArrowFunctionKey: 'LEFT'
                }[event.charactersIgnoringModifiers()] + '>'
        else:
            keypress = event.characters()

        self.command_parser.parse_key(keypress)

    def turnPointer(self, turn_direction):
        self.facing = self.facing + turn_direction
        self.updateCell(self.selectedCell)

    def movePointerTo(self, x, y):
        try:
            other = self.map.getCell(int(x), int(y))
            self.updateCell(self.selectedCell)
            self.selectedCell = other
            self.updateCell(self.selectedCell)
        except IndexError:
            pass

    def movePointer(self, dir):
        move = { Directions.North:(0,-1), Directions.South:(0,1), Directions.East:(1,0), Directions.West:(-1,0) }
        x, y = map(operator.add, move[dir], self.selectedCell.pos)
        self.movePointerTo(x, y)

    def changeWallInCurrentCell(self, dir, forward_in_list=True):
        idx = self.wall_types.index(self.selectedCell.getWall(dir)) + (1 if forward_in_list else -1)
        self.selectedCell.setWall(dir, self.wall_types[idx % len(self.wall_types)])

    def changeCurrentCell(self, forward_in_list=True):
        idx = self.cell_types.index(self.selectedCell.value) + (1 if forward_in_list else -1)
        self.selectedCell.value = self.cell_types[idx % len(self.cell_types)]

    def clearCurrentCell(self, includeWalls = True):
        self.selectedCell.value = 'clear'
        if includeWalls: self.clearCurrentCellWalls()

    def clearCurrentCellWalls(self):
        for dir in Directions: self.selectedCell.setWall(dir, 'clear')

    def setNote(self, note):
        self.selectedCell.notes = note

    def resizeMap(self, x, y):
        for i in range(int(x) - self.map.width):
            self.map.expandWidth()
        for i in range(int(y) - self.map.height):
            self.map.expandHeight()
        self.resize()
        self.unregisterEventListeners()
        self.registerEventListeners()
        self.window().setViewsNeedDisplay_(YES)

    def moveMap(self, dir):
        self.unregisterEventListeners()
        self.map.moveMap(dir)
        self.registerEventListeners()
        self.setNeedsDisplay_(YES)


